#include "OMIT_MeshUtil.h"

#include <fstream>
#include <array>
#include <filesystem>
#include <type_traits>

#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <range/v3/all.hpp>
#include <spdlog/spdlog.h>

#include <vtk-9.1/vtkDataSetAttributes.h>
#include <vtk-9.1/vtkPolyData.h>
#include <vtk-9.1/vtkDataArray.h>
#include <vtk-9.1/vtkPolyDataWriter.h>
#include <vtk-9.1/vtkSTLWriter.h>

#include "../../include/TopoDS/OMIT_MeshTraits.h"
#include "../../inc/TopoDS/OMIT_MeshForwardDecl.h"

#include "OMIT_AttrsTraits.h"
#include "OMIT_MeshAttrDS.h"

class vtkCharArray;
class vtkShortArray;
class vtkIntArray;
class vtkFloatArray;
class vtkDoubleArray;
class vtkUnsignedCharArray;
class vtkUnsignedShortArray;
class vtkUnsignedIntArray;

namespace glm
{
    using std::size_t;
#	if GLM_CONFIG_LENGTH_TYPE == GLM_LENGTH_SIZE_T
    typedef size_t length_t;
#	else
    typedef int length_t;
#	endif

    enum qualifier;

    template<length_t L, typename T, qualifier Q> struct vec;
    template<length_t C, length_t R, typename T, qualifier Q> struct mat;
    template<typename T, qualifier Q> struct qua;
}//namespace glm

namespace OMIT_NAMESPACE
{
    namespace detail
    {
        // 目前只支持写入以下三类属性：
        //  + SCALARS 标量属性
        //  + VECTORS 向量属性
        //  + TENSORS 张量属性
        // 函数参数可以通过 GetValue() 获得

        template <typename elem_type>
        struct _traitAttrValueInfo_meta
        {
            constexpr static bool IsSigned()    { return std::is_signed_v<elem_type>; }
            constexpr static bool Is8Bit()      { return std::is_same_v<elem_type, uint8_t> || std::is_same_v<elem_type, int8_t>; }
            constexpr static bool Is16Bit()     { return std::is_same_v<elem_type, uint16_t> || std::is_same_v<elem_type, int16_t>; }
            constexpr static bool Is32Bit()     { return std::is_same_v<elem_type, uint32_t> || std::is_same_v<elem_type, int32_t>; }
            constexpr static bool IsFloat()     { return std::is_same_v<elem_type, float>; }
            constexpr static bool IsDouble()    { return std::is_same_v<elem_type, double>; }
        };

        template <typename T>
        struct _traitAttrValueInfo : public _traitAttrValueInfo_meta<T>
        {
            using item_type = T;
            constexpr static int ItemCount() { return 1; }

            constexpr static vtkDataSetAttributes::AttributeTypes GetCategory()
            {
                static_assert(std::is_arithmetic_v<T>, "The value type value_type of attribute bust me arithmetic type if value_type are neither vector nor tensor!");
                return vtkDataSetAttributes::AttributeTypes::SCALARS;
            }
        }; 

        template <glm::length_t L, typename T>
        struct _traitAttrValueInfo<glm::vec<L, T>> : public _traitAttrValueInfo_meta<T>
        {
            constexpr static int ItemCount() { return L; }

            constexpr static vtkDataSetAttributes::AttributeTypes GetCategory()
            {
                return vtkDataSetAttributes::AttributeTypes::VECTORS;
            }
        };

        template <glm::length_t C, glm::length_t R, typename T>
        struct _traitAttrValueInfo<glm::mat<C, R, T>> : public _traitAttrValueInfo_meta<T>
        {
            constexpr static int ItemCount() { return C * R; }
        
            constexpr static vtkDataSetAttributes::AttributeTypes GetCategory()
            {
                return vtkDataSetAttributes::AttributeTypes::TENSORS;
            }
        };

        // 将 TAttr 的 value_type 转为 std::array，自定义 value_type 需要全特化此函数
        template <typename T>
        auto TransAttrToTuple(T t) -> std::enable_if_t<std::is_floating_point_v<T>, std::array<double, 1>>
        {
            return std::array<double, 1>{ t };
        }

        template <glm::length_t L, typename T>
        auto TransAttrToTuple(const glm::vec<L, T>& vec) -> std::array<double, L>
        {
            std::array<double, L> retArray;
            std::copy(glm::value_ptr(vec), glm::value_ptr(vec) + L, retArray.begin());
            return retArray;
        }

        template <glm::length_t C, glm::length_t R, typename T>
        auto TransAttrToTuple(const glm::mat<C, R, T>& mat)->std::array<double, C * R>
        {
            std::array<double, C * R> retArray;
            std::copy(glm::value_ptr(mat), glm::value_ptr(mat) + C * R, retArray.begin());
            return retArray;
        };

        // 将顶点或面的属性提取到 vtkDataArray 并赋值给 dataAttributes
        template <typename TD>
        void _extractAttrToPolyData(const std::vector<TD*>& meshDataBuffer,
                                    const std::string& attrNamePrefix,
                                    const std::tuple<>& attrsVal,
                                    vtkDataSetAttributes* dataAttributes,
                                    OMIT_MESHATTR_OUTPUT_NUMOPS attrOPs)
        {
            // do nothing
        }

        template <typename TD, typename TAttr, typename... TAttrRemains>
        void _extractAttrToPolyData(const std::vector<TD*>& meshDataBuffer,
                                    const std::string& attrNamePrefix,
                                    const std::tuple<TAttr, TAttrRemains...>& attrsVal,
                                    vtkDataSetAttributes* dataAttributes,
                                    OMIT_MESHATTR_OUTPUT_NUMOPS attrOPs)
        {
            // step 1. 检查 UUID 是否在 attrOPs 中
            if (attrOPs.count(TAttr::UUID()) != 0)
            {
                using attr_valType = typename TAttr::value_type;
                constexpr vtkDataSetAttributes::AttributeTypes attr_valCategory
                    = _traitAttrValueInfo<attr_valType>::GetCategory();

                // step 2. 如果在，则根据 TAttr 对 pMesh 中对应的顶点集合或面单元集合进行遍历，
                //         选出对应的元素，放到 vtkDataArray
                vtkSmartPointer<vtkDataArray> pDataArray = nullptr;
                if constexpr (_traitAttrValueInfo<attr_valType>::IsSigned())
                {
                    if constexpr (_traitAttrValueInfo<attr_valType>::Is8Bit())
                        pDataArray = vtkSmartPointer<vtkCharArray>::New();
                    else if constexpr (_traitAttrValueInfo<attr_valType>::Is16Bit())
                        pDataArray = vtkSmartPointer<vtkShortArray>::New();
                    else if constexpr (_traitAttrValueInfo<attr_valType>::Is32Bit())
                        pDataArray = vtkSmartPointer<vtkIntArray>::New();
                    else if constexpr (_traitAttrValueInfo<attr_valType>::IsFloat())
                        pDataArray = vtkSmartPointer<vtkFloatArray>::New();
                    else
                    {
                        static_assert(_traitAttrValueInfo<attr_valType>::IsDouble(), "Unknown data type!");
                        pDataArray = vtkSmartPointer<vtkDoubleArray>::New();
                    }
                }
                else
                {
                    if constexpr (_traitAttrValueInfo<attr_valType>::Is8Bit())
                        pDataArray = vtkSmartPointer<vtkUnsignedCharArray>::New();
                    else if constexpr (_traitAttrValueInfo<attr_valType>::Is16Bit())
                        pDataArray = vtkSmartPointer<vtkUnsignedShortArray>::New();
                    else
                    {
                        static_assert(_traitAttrValueInfo<attr_valType>::Is32Bit(), "Unknown data type!");
                        pDataArray = vtkSmartPointer<vtkUnsignedIntArray>::New();
                    }
                }

                std::string attr_valName = attrNamePrefix + TAttr::Name();
                pDataArray->SetName(attr_valName.c_str());
                pDataArray->SetNumberOfComponents(_traitAttrValueInfo<attr_valType>::ItemCount());

                for (const TD* pD : meshDataBuffer)
                {
                    const auto& attrVal = static_cast<const _MeshAttr_Base<TAttr>*>(pD)->GetValue();
                    std::array<double, _traitAttrValueInfo<attr_valType>::ItemCount()> attrArray 
                        = TransAttrToTuple(attrVal);
                    pDataArray->InsertNextTuple(attrArray.data());
                }

                // step 3. 将生成的 dataArray 放到 dataAttributes 里
                if constexpr (attr_valCategory == vtkDataSetAttributes::AttributeTypes::SCALARS)
                    dataAttributes->SetScalars(pDataArray);
                else if constexpr (attr_valCategory == vtkDataSetAttributes::AttributeTypes::VECTORS)
                    dataAttributes->SetVectors(pDataArray);
                else if constexpr (attr_valCategory == vtkDataSetAttributes::AttributeTypes::TENSORS)
                    dataAttributes->SetTensors(pDataArray);
            }

            auto attrVal = std::tuple<TAttrRemains...>{};
            _extractAttrToPolyData<TD, TAttrRemains...>(meshDataBuffer,
                                                        attrNamePrefix,
                                                        attrVal,
                                                        dataAttributes,
                                                        attrOPs);
        }
    }

    namespace DataIO
    {
        template <typename Traits>
        vtkSmartPointer<vtkPolyData> ConvertToVTKDS(std::shared_ptr<OMIT_NAMESPACE::TMesh<Traits>> pMesh,
                                                    OMIT_MESHATTR_OUTPUT_NUMOPS vertAttrOPs,
                                                    OMIT_MESHATTR_OUTPUT_NUMOPS faceAttrOPs)
        {
            using TV = OMIT_NAMESPACE::MeshTraits_V<Traits>;
            using TE = OMIT_NAMESPACE::MeshTraits_E<Traits>;
            using TF = OMIT_NAMESPACE::MeshTraits_F<Traits>;
            using TH = OMIT_NAMESPACE::MeshTraits_H<Traits>;

            // check if the TV contains the required attributes
            static_assert(detail::_is_tuple<typename TV::value_type>::value, "Invalid type traits of value_type!");
            static_assert(detail::_is_in_tuple<geomAttr::Attr_Coord3f, typename TV::value_type>::value,
                "The Vertex doesn't has the property 'geom::Attr_Coord3f'! "
                "Please inherit Vertex from TAttrVert<Traits, geom::Attr_Coord3f,...>'!");

            // step 0. prepare poly-face data
            vtkSmartPointer<vtkPolyData> pPolyData = vtkSmartPointer<vtkPolyData>::New();

            // step 1. prepare coordinate points
            vtkSmartPointer<vtkPoints> pPoints = vtkSmartPointer<vtkPoints>::New();
            const auto& vertList = pMesh->Vertices();
            for (const TV* pV : vertList)
            {
                // HACK: maybe time-consuming
                pPoints->InsertPoint(pMesh->Index(pV), glm::value_ptr(pV->_coord3f));
            }
            pPolyData->SetPoints(pPoints);

            // step 2. prepare cells
            vtkSmartPointer<vtkCellArray> pCellArray = vtkSmartPointer<vtkCellArray>::New();
            const auto& faceList = pMesh->Faces();
            for (const TF* pF : faceList)
            {
                // HACK: maybe time-consuming
                std::vector<vtkIdType> faceVertIDs = pF->AdjHalfEdges()
                    | ranges::views::transform([pMesh](const TH* pH)->size_t
                        { return pMesh->Index(pH->Origin()); })
                    | ranges::to<std::vector<vtkIdType>>();
                pCellArray->InsertNextCell(pF->Degree(), faceVertIDs.data());
            }
            pPolyData->SetPolys(pCellArray);

            // step 3. prepare data per vertices
            auto TV_val = typename TV::value_type{};
            _extractAttrToPolyData(pMesh->Vertices(),
                                    "Vert",
                                    TV_val,
                                    pPolyData->GetPointData(),
                                    vertAttrOPs);

            // step 4. prepare data per cell (face)
            auto TF_val = typename TF::value_type{};
            _extractAttrToPolyData(pMesh->Faces(),
                                    "Cell",
                                    TF_val,
                                    pPolyData->GetCellData(),
                                    faceAttrOPs);

            return pPolyData;
        }

        template <typename Traits>
        vtkSmartPointer<vtkPolyData> ConvertToVTKDS(const std::vector<std::shared_ptr<TMesh<Traits>>>& pMeshList,
                                                    OMIT_MESHATTR_OUTPUT_NUMOPS vertAttrOPs,
                                                    OMIT_MESHATTR_OUTPUT_NUMOPS faceAttrOPs)
        {

        }
    }
}